﻿using System;
using HtmlAgilityPack;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using Document = HtmlAgilityPack.HtmlDocument;
using Node = HtmlAgilityPack.HtmlNode;
using DotNetBurner.Core;

namespace DotNetBurner.Web
{
    /// <summary>
    /// Description of HtmlUtil.
    /// </summary>
    public class HtmlUtil
    {
        private static readonly Regex TrackbackExpression = new Regex("trackback:ping=\"([^\"]+)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        Document _document;
        ICollection<string> _xPaths;

        public HtmlUtil(string content, ICollection<String> xPaths)
        {
            _document = new Document();
            _document.LoadHtml(content);

            _xPaths = xPaths;
        }

        public HtmlUtil(string content)
            : this(content, GetDefaultXPaths())
        {
        }

        public static string[] GetDefaultXPaths()
        {
            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data/site_content_xpaths.txt");

            if (File.Exists(path))
            {
                return File.ReadAllText(path).Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            }

            return new string[] { "" };
        }

        public string GetTitle()
        {
            Node titleNode = _document.DocumentNode.SelectSingleNode("//title");
            return (titleNode == null) ? null : HtmlEntity.DeEntitize(titleNode.InnerText).Trim();
        }

        public string GetKeyWords()
        {
            Node node = _document.DocumentNode.SelectSingleNode(".//meta[@name=\"keywords\"]");

            if (node != null)
            {
                string keywords = node.GetAttributeValue("content", String.Empty);
                if (keywords.Length > 0)
                    return HtmlEntity.DeEntitize(keywords).Trim();
            }

            return String.Empty;
        }

        public string GetSummary(int minSize, int maxSize)
        {
            string content = GetContent();

            return content.GetSummary(minSize, maxSize);
        }

        public string GetContent()
        {
            string content = TryToFindContent(_document.DocumentNode);

            if (!String.IsNullOrEmpty(content))
            {
                return HtmlEntity.DeEntitize(ConvertToText(content).RemoveDoubleSpaces());
            }
            else
            {
                //Try meta description, maybe we're lucky!
                Node node = _document.DocumentNode.SelectSingleNode(".//meta[@name=\"description\"]");

                if (node == null)
                    node = _document.DocumentNode.SelectSingleNode(".//meta[@name=\"DESCRIPTION\"]");

                if (node != null)
                {
                    string metaDesc = node.GetAttributeValue("content", null);

                    if (!string.IsNullOrEmpty(metaDesc))
                        return metaDesc;
                }
            }

            return String.Empty;
        }

        string TryToFindContent(Node node)
        {
            foreach (string xPath in _xPaths)
            {
                //select all nodes?
                if (xPath.StartsWith("ALL", StringComparison.InvariantCulture))
                {
                    string path = xPath.Remove(0, 3).Trim();

                    HtmlNodeCollection collection = node.SelectNodes(path);

                    if (collection != null && collection.Count > 0)
                    {
                        string content = String.Empty;

                        foreach (Node n in collection)
                        {
                            content = String.Concat(content, n.InnerHtml);
                        }

                        return content;
                    }
                }
                else
                {
                    Node contentNode = node.SelectSingleNode(xPath);

                    if (contentNode != null)
                        return contentNode.InnerHtml;
                }
            }

            return String.Empty;
        }

        public string GetTrackbackUrl(string url)
        {
            //check RDF (most common i guess...)
            Match match = TrackbackExpression.Match(_document.DocumentNode.InnerHtml);
            string trackbackUrl = ((match != null) && (match.Groups != null) && (match.Groups.Count > 1)) ? match.Groups[1].Value.Trim() : null;

            if (trackbackUrl != null)
                return trackbackUrl.Trim();

            //guess for Typepad
            Node targetNode = _document.DocumentNode.SelectSingleNode(".//div[@class=\"trackbacks-info\"]/p/span[@class=\"trackbacks-link\"]");

            if (targetNode != null)
            {
                return targetNode.InnerText;
            }

            //This is for B2Evolution
            targetNode = _document.DocumentNode.SelectSingleNode(".//p[@class=\"trackback_url\"]/a");

            if ((targetNode != null) && targetNode.HasAttributes)
            {
                return targetNode.GetAttributeValue("href", null);
            }

            //maybe wordpress?
            targetNode = _document.DocumentNode.SelectSingleNode("//meta[@name=\"generator\"]");

            if (targetNode != null)
            {
                string generator = targetNode.GetAttributeValue("content", string.Empty).Trim();

                if (generator.StartsWith("WordPress", StringComparison.OrdinalIgnoreCase))
                {
                    return String.Concat(url, (!url.EndsWith("/", StringComparison.OrdinalIgnoreCase) ? "/" : string.Empty), "trackback/");
                }
            }

            return null;
        }

        public static string ConvertToText(string html)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);

            StringWriter sw = new StringWriter (CultureInfo.InvariantCulture);
            ConvertTo(doc.DocumentNode, sw);
            sw.Flush();
            return sw.ToString();
        }

        static void ConvertContentTo(HtmlNode node, TextWriter outText)
        {
            foreach (HtmlNode subnode in node.ChildNodes)
            {
                ConvertTo(subnode, outText);
            }
        }

        static void ConvertTo(HtmlNode node, TextWriter outText)
        {
            string html;
            switch (node.NodeType)
            {
                case HtmlNodeType.Comment:
                    // don't output comments
                    break;

                case HtmlNodeType.Document:
                    ConvertContentTo(node, outText);
                    break;

                case HtmlNodeType.Text:
                    // script and style must not be output
                    string parentName = node.ParentNode.Name;
                    if ((parentName == "script") || (parentName == "style"))
                        break;

                    // get text
                    html = ((HtmlTextNode)node).Text;

                    // is it in fact a special closing node output as text?
                    if (HtmlNode.IsOverlappedClosingElement(html))
                        break;

                    // check the text is meaningful and not a bunch of whitespaces
                    if (html.Trim().Length > 0)
                    {
                        outText.Write(HtmlEntity.DeEntitize(html));
                    }
                    break;

                case HtmlNodeType.Element:
                    switch (node.Name)
                    {
                        case "p":
                            // treat paragraphs as crlf
                            outText.Write("\r\n");
                            break;
                    }

                    if (node.HasChildNodes)
                    {
                        ConvertContentTo(node, outText);
                    }
                    break;
            }
        }
    }
}
